/*   The MIT License
 *   
 *   Carina Common
 *   Copyright (c) 2010-2011 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef _CES_FILE_HH
#define _CES_FILE_HH

#include "carina/common/global.hh"
#include "carina/common/types.hh"
#include "carina/common/memory.hh"
#include "carina/common/patterns.hh"
#include "carina/common/ces-file-format.hh"
#include "carina/common/containers.hh"

#include <vector>

#include <fstream>

// TODO: Cloning

namespace Carina
{
size_t copy_string_to_fixed(char* dst, size_t dst_size, const char* src);

class CESBoneAnim
{
    CESBoneAnimHeader           m_Header;
public:
    CESBoneAnim();
     ~CESBoneAnim();

    CESBoneAnim(const CESBoneAnim& ces_banim);
    CESBoneAnim& operator=(const CESBoneAnim& ces_banim);

    float* getTransform(size_t i = 0);
};

class CESBone
{
    CESBoneHeader               m_Header;
    std::vector<bone_child_t>   m_Children;
public:
    CESBone();
     ~CESBone();

    CESBone(const CESBone& ces_bone);
    CESBone& operator=(const CESBone& ces_bone);

    void setChildren(const DataVector<bone_child_t>& children);
    size_t getChildrenCount() const;
    bone_child_t getChild(size_t idx) const;

    float* getInvTransform(size_t i = 0);
    float* getTransform(size_t i = 0);

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESSkeleton
{
    CESSkeletonHeader               m_Header;
    std::vector<CESBone>            m_Nodes;
public:
    CESSkeleton();
     ~CESSkeleton();

    CESSkeleton(const CESSkeleton& ces_skel);
    CESSkeleton& operator=(const CESSkeleton& ces_skel);

    bool empty() const;

    CESBone& getNode(size_t i);
    size_t getBoneCount() const;
    size_t getNodeCount() const;
    void setCount(size_t bones, size_t nodes_total);

    void setRoot(size_t idx); // fixme
    size_t getRoot() const;

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

typedef shared_ptr<CESSkeleton> CESSkeletonPtr;

class CESAnimation: public non_copyable
{
    CESAnimationHeader              m_Header;
    CESSkeletonPtr                  m_Skeleton;
    std::vector<float>              m_Keyframes;
    std::vector<CESBoneAnim>        m_BoneAnims;
public:
    CESAnimation(const CESSkeletonPtr& skel);
    CESAnimation(const string& name, const CESSkeletonPtr& skel);

    CESAnimation(const CESAnimation& ces_anim, const CESSkeletonPtr& skel);
    
    void setKeyframes(const std::vector<float>& k);
    size_t getKeyframesCount() const;
    float getKeyframeTime(size_t idx) const;

    CESBoneAnim& getNode(size_t k, size_t i);
    size_t getBoneCount() const;
    size_t getNodeCount() const;

    string getName() const;

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

typedef shared_ptr<CESAnimation> CESAnimationPtr;
typedef shared_ptr<const CESAnimation> CESAnimationConstPtr;

class CESAnimationSection: public non_copyable
{
    CESAnimationSectionHeader       m_Header;
    std::vector<CESAnimationPtr>    m_Animations;
public:
    CESAnimationSection();

    void addAnimation(const CESAnimationPtr& anim);
    CESAnimationPtr getAnimation(size_t idx);
    CESAnimationConstPtr getAnimation(size_t idx) const;
    size_t getCount() const;
    void clear();

    bool read(std::istream& is, const CESSkeletonPtr& skel);
    void write(std::ostream& os) const;
};

template<class T> struct CESAttrInfo;

#define CES_ATTR_ENUM_BIND(T, type_enum) \
    template<> struct CESAttrInfo<T> { \
        enum { type_name = type_enum }; \
    };

CES_ATTR_ENUM_BIND(float, CES_FLOAT)
CES_ATTR_ENUM_BIND(int32, CES_INT)
CES_ATTR_ENUM_BIND(int16, CES_SHORT)
CES_ATTR_ENUM_BIND(int8, CES_BYTE)
CES_ATTR_ENUM_BIND(uint32, CES_UINT)
CES_ATTR_ENUM_BIND(uint16, CES_USHORT)
CES_ATTR_ENUM_BIND(uint8, CES_UBYTE)
CES_ATTR_ENUM_BIND(quat_t, CES_QUATERNION)
CES_ATTR_ENUM_BIND(dquat_t, CES_DUAL_QUATERNION)
CES_ATTR_ENUM_BIND(vec2_t, CES_VECTOR2)
CES_ATTR_ENUM_BIND(vec3_t, CES_VECTOR3)
CES_ATTR_ENUM_BIND(vec4_t, CES_VECTOR4)
CES_ATTR_ENUM_BIND(mat3_t, CES_MATRIX3)
CES_ATTR_ENUM_BIND(mat4_t, CES_MATRIX4)
CES_ATTR_ENUM_BIND(filename_t, CES_FILENAME)
CES_ATTR_ENUM_BIND(fixed_string_t, CES_FIXED_STRING)

class CESAttributeValue;
typedef shared_ptr<CESAttributeValue> CESAttributeValuePtr;

class CESAttributeValue
{
public:
    virtual CESAttributeValuePtr clone() const=0;

    virtual bool read(std::istream& fs) = 0;
    virtual void write(std::ostream& fs) const = 0;
};

template<class T>
class CESAttributeVar: public CESAttributeValue
{
    T                   m_Value;
public:
    CESAttributeVar() {}

    CESAttributeVar(const T& val)
        :   m_Value(val) {}

    virtual ~CESAttributeVar() {}

    CESAttributeVar(const CESAttributeVar& ces_avar)
        :   m_Value(ces_avar.m_Value) {}

    CESAttributeVar& operator=(const CESAttributeVar& ces_avar)
    {
        m_Value = ces_avar.m_Value;
        return *this;
    }

    void get(T& t) const
    {
        t = m_Value;
    }

    virtual CESAttributeValuePtr clone() const
    {
        return make_aligned_shared<CESAttributeVar<T>>(*this);
    }

    virtual bool read(std::istream& fs)
    {
        fs.read(reinterpret_cast<char*>(&m_Value), sizeof(T));
        return fs.good();
    }

    virtual void write(std::ostream& fs) const
    {
        fs.write(reinterpret_cast<const char*>(&m_Value), sizeof(T));
    }
};

template<class T> class CESAttributeArrayValue;

class CESAttributeArray: public CESAttributeValue
{
    CESArrayAttributeHeader m_Header;
    CESAttributeValuePtr    m_Array;
public:
    CESAttributeArray();
    virtual ~CESAttributeArray();

    CESAttributeArray(const CESAttributeArray& ces_arr);
    CESAttributeArray& operator=(const CESAttributeArray& ces_arr);

    size_t size() const;

    CESVarType getType() const;

    template<template<class U, class A> class TArray, typename T, typename TAlloc>
    void set(const TArray<T, TAlloc >& arr);

    template<template<class U, class A> class TArray, typename T, typename TAlloc>
    bool get(TArray<T, TAlloc >& arr) const;

    virtual CESAttributeValuePtr clone() const;

    virtual bool read(std::istream& fs);
    virtual void write(std::ostream& fs) const;
};

template<template<class U, class A> class TArray, class T, class TAlloc>
class CESAttributeArrayValue< TArray<T, TAlloc> >: public CESAttributeValue, public non_copyable
{
    CESAttributeArray&          m_Parent;
    TArray<T, TAlloc>           m_Array;
public:
    CESAttributeArrayValue(CESAttributeArray& parent)
        :   m_Parent(parent) {}

    CESAttributeArrayValue(CESAttributeArray& parent, const TArray<T, TAlloc>& arr)
        :   m_Parent(parent),
            m_Array(arr) {}

    virtual ~CESAttributeArrayValue() {}

    CESAttributeArrayValue(CESAttributeArray& parent, const CESAttributeArrayValue& ces_attr)
        :   m_Parent(parent),
            m_Array(ces_attr.m_Array)
    {
    }

    CESAttributeArrayValue& operator=(const CESAttributeArrayValue& ces_attr)
    {
        m_Array = ces_attr.m_Array;
        return *this;
    }

    void get(TArray<T, TAlloc>& arr) const
    {
        arr = m_Array;
    }

    virtual CESAttributeValuePtr clone() const
    {
        return CESAttributeValuePtr();
    }

    virtual bool read(std::istream& fs)
    {
        m_Array.resize(m_Parent.size());
        fs.read(reinterpret_cast<char*>(m_Array.get()), sizeof(T)*m_Array.size());
        return fs.good();
    }

    virtual void write(std::ostream& fs) const
    {
        fs.write(reinterpret_cast<const char*>(m_Array.get()), sizeof(T)*m_Array.size());
    }
};

template<template<class U, class A> class TArray, typename T, typename TAlloc>
inline void CESAttributeArray::set(const TArray<T, TAlloc>& arr)
{
    typedef CESAttributeArrayValue< TArray<T, TAlloc> > attr_type;
    m_Array = make_aligned_shared<attr_type>(*this, arr);
    m_Header.size = arr.size();
    m_Header.type = CESAttrInfo<T>::type_name;
}

template<template<class U, class A> class TArray, typename T, typename TAlloc>
inline bool CESAttributeArray::get(TArray<T, TAlloc>& arr) const
{
    if(m_Header.type != CESAttrInfo<T>::type_name)
        return false;
    static_cast<const CESAttributeArrayValue< TArray<T, TAlloc> >&>(*m_Array).get(arr);
    return true;
}

typedef shared_ptr<CESAttributeValue> CESAttributeValuePtr;

class CESAttribute
{
    CESAttributeHeader      m_Header;
    CESAttributeValuePtr    m_Value;
public:
    CESAttribute();
    CESAttribute(const string& name);
     ~CESAttribute();

    CESAttribute(const CESAttribute& ces_attr);
    CESAttribute& operator=(const CESAttribute& ces_attr);

    bool operator==(const string& attr_name) const;

    string getName() const;

    void set(const char* str);
    void set(const string& str);

    template<typename T>
    void set(const T& t)
    {
        m_Value = make_aligned_shared<CESAttributeVar<T>>(t);
        m_Header.type = CESAttrInfo<T>::type_name;
    }

    template<template<class U, class A> class TArray, class TAlloc>
    void set(const TArray<string, TAlloc>& arr)
    {
        TArray<fixed_string_t, TAlloc> farr(arr.size());
        for(size_t i = 0; i < arr.size(); ++i)
            copy_string_to_fixed(farr[i].str, sizeof(fixed_string_t().str), arr[i].c_str());
        set(farr);
    }

    template<template<class U, class A> class TArray, typename T, typename TAlloc>
    void set(const TArray<T, TAlloc>& arr)
    {
        m_Value = make_aligned_shared<CESAttributeArray>();
        static_cast<CESAttributeArray&>(*m_Value).set(arr);
        m_Header.type = CES_ARRAY;
    }

    bool get(string& str) const;

    template<typename T>
    bool get(T& t) const
    {
        if(m_Header.type != CESAttrInfo<T>::type_name)
            return false;
        static_cast<const CESAttributeVar<T>&>(*m_Value).get(t);
        return true;
    }

    template<template<class U, class A> class TArray, class TAlloc>
    bool get(TArray<string, TAlloc>& arr) const
    {
        TArray<fixed_string_t, DAAllocator<fixed_string_t> > farr;
        if(!get(farr))
            return false;
        arr.resize(farr.size());
        for(size_t i = 0; i < farr.size(); ++i)
            arr[i] = farr[i].str;
        return true;
    }

    template<template<class U, class A> class TArray, typename T, typename TAlloc>
    bool get(TArray<T, TAlloc>& t) const
    {
        if(m_Header.type != CES_ARRAY)
            return false;
        return static_cast<const CESAttributeArray&>(*m_Value).get(t);
    }

    CESVarType getType() const;
    CESVarType getArrayType() const;

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESAttributeContainer
{
    CESAttributeContainerHeader         m_Header;
    std::vector<CESAttribute>           m_Attributes;
public:
    CESAttributeContainer();
     ~CESAttributeContainer();

    CESAttributeContainer(const CESAttributeContainer& ces_ac);
    CESAttributeContainer& operator=(const CESAttributeContainer& ces_ac);

    template<typename T>
    void setAttribute(const string& name, const T& value)
    {
        for(size_t i = 0; i < m_Attributes.size(); ++i)
            if(m_Attributes[i] == name)
            {
                m_Attributes[i].set(value);
                return;
            }
        m_Attributes.push_back(CESAttribute(name));
        m_Attributes.back().set(value);
        ++m_Header.attributes;
    }

    template<template<class U, class A> class TArray, class T, typename TAlloc>
    void setAttribute(const string& name, const TArray<T, TAlloc>& arr)
    {
        for(size_t i = 0; i < m_Attributes.size(); ++i)
            if(m_Attributes[i] == name)
            {
                m_Attributes[i].set(arr);
                return;
            }
        m_Attributes.push_back(CESAttribute(name));
        m_Attributes.back().set(arr);
        ++m_Header.attributes;
    }

    template<typename T>
    bool getAttribute(const string& name, T& value) const
    {
        for(size_t i = 0; i < m_Attributes.size(); ++i)
            if(m_Attributes[i] == name)
                return m_Attributes[i].get(value);
        return false;
    }

    size_t getAttributeCount() const;
    CESAttribute& getAttribute(size_t idx);
    CESAttribute* getAttribute(const string& name);

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESSubmesh
{
    CESSubmeshHeader            m_Header;
    CESAttributeContainer       m_Attributes;
public:
    CESSubmesh();
    ~CESSubmesh();

    CESSubmesh(const CESSubmesh& ces_sm);
    CESSubmesh& operator=(const CESSubmesh& ces_sm);

    void setCount(size_t c);
    void setOffset(size_t c);

    size_t getCount() const;
    size_t getOffset() const;

    void setMaterial(const string& name);
    string getMaterial() const;

    CESAttributeContainer& attributes();

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

typedef std::vector<indice_t> IndicesArray;
typedef std::vector<float>    PointDataArray;

class CESMesh
{
    CESSkeletonPtr              m_Skeleton;
    std::vector<indice_t>       m_Indices;
    std::vector<float>          m_PointData;
    std::vector<CESSubmesh>     m_Submeshes;
    CESAnimationSection         m_Animations;
public:
    CESMesh();
     ~CESMesh();

    CESMesh(const CESMesh& ces_mesh);
    CESMesh& operator=(const CESMesh& ces_mesh);

    bool hasSkeleton() const;
    CESSkeletonPtr getSkeleton();

    CESAnimationSection& getAnimations();

    CESAnimationPtr addAnimation(const string& animation);

    CESSubmesh& addSubmesh();
    CESSubmesh& getSubmesh(size_t idx);
    size_t getSubmeshCount() const;

    void setIndices(const IndicesArray& indices);
    void setData(const PointDataArray& data);
    
    const IndicesArray& getIndices() const;
    const PointDataArray& getPointData() const;

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESMeshInfo;
typedef shared_ptr<CESMeshInfo> CESMeshInfoPtr;

class CESMeshInfo
{
    bool    m_InPlace;
public:
    CESMeshInfo(bool in_place);
    virtual ~CESMeshInfo();

    CESMeshInfo(const CESMeshInfo& ces_mi);
    CESMeshInfo& operator=(const CESMeshInfo& ces_mi);

    bool isInPlace() { return m_InPlace; }

    virtual CESMeshInfoPtr clone() const=0;

    virtual bool read(std::istream& is)=0;
    virtual void write(std::ostream& os) const=0;
};

class CESMeshReference
{
    CESMeshReferenceHeader  m_Header;
    CESMeshInfoPtr          m_MeshInfo;
public:
    CESMeshReference();
    CESMeshReference(const string& name, const CESMeshInfoPtr& mesh_info);
     ~CESMeshReference();

    CESMeshReference(const CESMeshReference& ces_mref);
    CESMeshReference& operator=(const CESMeshReference& ces_mref);

    string getMeshName() const;
    void setMeshName(const string& name);

    CESMeshInfoPtr getMeshInfo();
    void setMeshInfo(const CESMeshInfoPtr& mesh_info);

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESMeshInPlace: public CESMeshInfo
{
    CESMesh     m_Mesh;
public:
    CESMeshInPlace();
     ~CESMeshInPlace();

    CESMeshInPlace(const CESMeshInPlace& ces_mref);
    CESMeshInPlace& operator=(const CESMeshInPlace& ces_mref);

    CESMesh& getMesh();

    virtual CESMeshInfoPtr clone() const;

    virtual bool read(std::istream& is);
    virtual void write(std::ostream& os) const;
};

typedef shared_ptr<CESMeshInPlace> CESMeshInPlacePtr;

class CESMeshFromFile: public CESMeshInfo
{
    filename_t  m_FileName;
public:
    CESMeshFromFile();
    CESMeshFromFile(const string& filename);
     ~CESMeshFromFile();

    CESMeshFromFile(const CESMeshFromFile& ces_mref);
    CESMeshFromFile& operator=(const CESMeshFromFile& ces_mref);

    string getFileName() const;
    void setFileName(const string& name);

    virtual CESMeshInfoPtr clone() const;

    virtual bool read(std::istream& is);
    virtual void write(std::ostream& os) const;
};

typedef shared_ptr<CESMeshFromFile> CESMeshFromFilePtr;

class CESGeometrySection
{
    CESGeometrySectionHeader            m_Header;
    std::vector<CESMeshReference>       m_MeshRefs;
public:
    CESGeometrySection();
     ~CESGeometrySection();

    CESGeometrySection(const CESGeometrySection& ces_geom);
    CESGeometrySection& operator=(const CESGeometrySection& ces_geom);

    void addMeshReference(const string& name, const CESMeshInfoPtr& mesh_info);
    CESMeshReference* getMeshReference(const string& name);
    CESMeshReference& getMeshReference(size_t idx);
    size_t getMeshRefCount() const;

    void removeMeshReference(size_t idx);
    void removeMeshReference(size_t lhs, size_t rhs);

    void clear();

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};


typedef CESAttributeContainer CESEntity;

class CESEntitySection
{
    CESEntitySectionHeader      m_Header;
    std::vector<CESEntity>      m_Entities;
public:
    CESEntitySection();
     ~CESEntitySection();

    CESEntitySection(const CESEntitySection& ces_ent);
    CESEntitySection& operator=(const CESEntitySection& ces_ent);

    CESEntity& addEntity();
    CESEntity* getEntity(const string& name);
    CESEntity& getEntity(size_t idx);
    size_t getEntityCount() const;

    void removeEntity(size_t idx);
    void removeEntity(size_t lhs, size_t rhs);

    void clear();

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESNode
{
    CESNodeHeader               m_Header;
    CESAttributeContainer       m_Attributes;
    std::vector<CESNode*>       m_Children;
public:
    CESNode();
     ~CESNode();

    CESNode(const CESNode& ces_node);
    CESNode& operator=(const CESNode& ces_node);

    CESAttributeContainer& attributes();

    CESNode& addChild();
    CESNode& getChild(size_t idx);

    size_t getChildrenCount() const;

    void removeChild(size_t idx);
    void removeChildren(size_t lhs, size_t rhs);

    void clear();

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESGlobalLightSection
{
    CESGlobalLightSectionHeader         m_Header;
    CESAttributeContainer               m_AmbientLight;
    std::vector<CESAttributeContainer>  m_GlobalLightSources;
public:
    CESGlobalLightSection();
     ~CESGlobalLightSection();

    CESGlobalLightSection(const CESGlobalLightSection& ces_gls);
    CESGlobalLightSection& operator=(const CESGlobalLightSection& ces_gls);

    CESAttributeContainer& addGlobalLight();
    CESAttributeContainer& getGlobalLight(size_t idx);
    
    size_t getGlobalLightCount() const;
    
    void removeGlobalLight(size_t idx);
    void removeGlobalLight(size_t lhs, size_t rhs);
    
    void clear();

    void getAmbientLight(vec3_t& color) const;
    void setAmbientLight(const vec3_t& color);

    bool read(std::istream& is);
    void write(std::ostream& os) const;
};

class CESFile
{
    CESHeader               m_Header;
    CESGeometrySection      m_Geometry;
    CESEntitySection        m_Entities;
    CESGlobalLightSection   m_Lighting;
    CESNode                 m_Root;
public:
    CESFile();
     ~CESFile();

    CESFile(const CESFile& ces_file);
    CESFile& operator=(const CESFile& ces_file);

    CESNode& getRoot();

    CESGeometrySection& getGeometrySection();
    CESEntitySection& getEntitySection();
    CESGlobalLightSection& getGlobalLightSection();

    void clear();

    bool save(const string& filename) const;
    bool open(const string& filename);
};

typedef CESMesh CEMMesh;

class CEMFile
{
    CEMHeader               m_Header;
    CEMMesh                 m_Mesh;
public:
    CEMFile();
     ~CEMFile();

    CEMFile(const CEMFile& cem_file);
    CEMFile& operator=(const CEMFile& cem_file);

    CEMMesh& getMesh();

    bool save(const string& filename) const;
    bool open(const string& filename);
};
}

#endif /* _CES_FILE_HH */
